﻿namespace Contoso.ActiveDirectory {

    #region Usings
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.DirectoryServices;
    using System.Globalization;
    using System.Diagnostics;
    using System.Threading; 
    #endregion

    /// <summary>
    /// The NTDSConnection object is a child of the NTDSSettings object within a Server object within an AD Site object
    /// </summary>
    [Serializable]
    public class NTDSConnection : ADBaseObjectProperties {

        #region Members
        #region Fields
        public static readonly new string[] AttributesToGet;

        public static readonly string ObjectCategoryShortName = "nTDSConnection"; 
        #endregion

        public bool EnabledConnection { get; private set; }

        public string FromServer { get; private set; }

        public InstanceTypeFlags InstanceType { get { return instanceType; } }
        private InstanceTypeFlags instanceType;

        public NTDSConnectionOptionsFlags Options { get { return options; } }
        private NTDSConnectionOptionsFlags options;

        /// <summary>
        /// Key: partition name  Value: Replication reasons
        /// </summary>
        public IReadOnlyDictionary<string, NTDSReplicationReasonFlags> PartitionList { get; private set; }

        public byte[] Schedule { get; private set; }
        public string TransportType { get; private set; }
        #endregion

        #region Constructors
        static NTDSConnection() {
            var attributesToGet = new List<string>();
            attributesToGet.AddRange(new string[]
            {
                "enabledConnection",
                "fromServer",
                "ms-DS-ReplicatesNCReason",
                "options",
                "schedule",
                "transportType"
            });

            attributesToGet.AddRange(ADBaseObjectProperties.AttributesToGet);

            AttributesToGet = attributesToGet.Distinct().ToArray();
            attributesToGet = null;
        }
        public NTDSConnection() {
            this.Initialize();
        } 

        public NTDSConnection(DirectoryEntry directoryEntry, bool destroyInputObject = true)
            : base(directoryEntry, destroyInputObject) {
                if (this.GetType().Name == typeof(NTDSConnection).Name) {
                this.Attributes = null;
            }
        }

        public NTDSConnection(SearchResult result, bool destroyInputObject = true)
            : base(result, destroyInputObject) {
                if (this.GetType().Name == typeof(NTDSConnection).Name) {
                this.Attributes = null;
            }
        }

        public NTDSConnection(List<string> ldifOutput)
            : base(ldifOutput) {
                if (this.GetType().Name == typeof(NTDSConnection).Name) {
                this.Attributes = null;
            }
        }
        #endregion

        #region Methods
        protected override void Initialize() {
            base.Initialize();

            this.FromServer = string.Empty;
            instanceType = InstanceTypeFlags.Undefined;
            options = NTDSConnectionOptionsFlags.Undefined;
            this.TransportType = string.Empty;
            this.PartitionList = new Dictionary<string, NTDSReplicationReasonFlags>(StringComparer.OrdinalIgnoreCase);
        }
        protected override void ProcessResults() {

            try {
                base.ProcessResults();

                if (this.Attributes.ContainsKey("enabledConnection")) {
                    this.EnabledConnection = Convert.ToBoolean(this.Attributes["enabledConnection"][0]);
                }
                if (this.Attributes.ContainsKey("fromServer")) {
                    this.FromServer = this.Attributes["fromServer"][0].ToString();
                }
                if (this.Attributes.ContainsKey("instanceType")) {
                    Enum.TryParse<InstanceTypeFlags>(this.Attributes["instanceType"][0].ToString(), result: out instanceType);
                }
                if (this.Attributes.ContainsKey("schedule")) {
                    this.Schedule = (byte[])this.Attributes["schedule"][0];
                }

                // sample data: "B:8:00000002:DC=domain,DC=acme,DC=com"
                if (this.Attributes.ContainsKey("ms-DS-ReplicatesNCReason")) {
                    var partitionList = new Dictionary<string, NTDSReplicationReasonFlags>(StringComparer.OrdinalIgnoreCase);
                    for (int index = 0; index < this.Attributes["ms-DS-ReplicatesNCReason"].Length; index++) {
                        string listEntry = this.Attributes["ms-DS-ReplicatesNCReason"][index].ToString();
                        int reasonCode = 0;
                        if (listEntry.Count(x => x == ':') == 3) {
                            if (int.TryParse(listEntry.Substring(4, 8), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out reasonCode)) {
                                string partitionName = listEntry.Substring(listEntry.LastIndexOf(":") + 1);
                                if (!partitionList.ContainsKey(partitionName)) {
                                    partitionList.Add(partitionName, (NTDSReplicationReasonFlags)reasonCode);
                                }
                                else {
                                    Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} Warning: PartitionList already has an entry for partition name: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), partitionName));
                                }
                            }
                        }
                    }
                    this.PartitionList = partitionList;
                }
                if (this.Attributes.ContainsKey("options")) {
                    Enum.TryParse<NTDSConnectionOptionsFlags>(this.Attributes["options"][0].ToString(), result: out options);
                }
                if (this.Attributes.ContainsKey("transportType")) {
                    this.TransportType = this.Attributes["transportType"][0].ToString();
                }

                this.IsValid = !string.IsNullOrWhiteSpace(this.DN)
                    && this.ObjectClass.Any(x => string.Equals(x, ObjectCategoryShortName, StringComparison.OrdinalIgnoreCase));
            }
            catch (Exception e) {
                Debug.WriteLine(string.Format("{0}  [ThreadID: {1}]  {2} Error: {3}", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, CustomExtensions.CurrentMethodName(), e.VerboseExceptionString()));
                throw;
            }
        }
        [DebuggerStepThroughAttribute]
        public string ToFormattedString() {
            var info = new StringBuilder();

            info.AppendLine(string.Format("  Connection: {0}", this.ObjectGuid));
            info.AppendLine(string.Format("  Enabled: {0}", this.EnabledConnection));
            info.AppendLine(string.Format("  From server: {0}", this.FromServer ?? "NULL"));
            info.AppendLine(string.Format("  Instance type: {0}", this.InstanceType));
            info.AppendLine(string.Format("  NTDSConnectionOptions: {0}", this.Options));
            info.AppendLine(string.Format("  Transport Type: {0}", this.TransportType ?? "NULL"));
            info.AppendLine(string.Format("  Partition list:"));
            foreach (var reason in this.PartitionList) {
                info.AppendLine(string.Format("  - Partition: {0} Replication reason: {1}", reason.Key, reason.Value));
            }

            return info.ToString();
        }
        [DebuggerStepThroughAttribute]
        public override string ToString() {
            string details = this.DN;
            if (string.IsNullOrWhiteSpace(details)) details = "N/A";
            return details;
        } 
        #endregion
    }
}
